package br.ufs.dcomp.lsp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.concurrent.LinkedBlockingQueue;

/*Classe do cliente LSP*/
public class LSPClient implements Sender {

	boolean active = true;
	short sequenceNumber = 0;
	
	short connId;
	InetAddress address;
	int port;
	LSPParams params;

	static DatagramSocket serverSocket;

	short packetWaitingForACK = -1;
	LinkedBlockingQueue<Pack> packetsReceived;
	LinkedBlockingQueue<Pack> packetsToSend;
	
	Thread sender;

	public LSPClient(String host, int port, LSPParams params) {
		try {
			
			this.address = Inet4Address.getByName(host);
			this.port = port;
			try {
				this.serverSocket = new DatagramSocket(port, InetAddress.getByName(host));
			} catch (SocketException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			if (params == null)
				this.params = LSPParams.getDefault();
			else
				this.params = params;
			this.packetsToSend = new LinkedBlockingQueue<Pack>();
			this.packetsReceived = new LinkedBlockingQueue<Pack>();
			
			// Iniciando as threads
			this.sender = new Thread(new SendingHandler(this, this.serverSocket, this.params));
			this.sender.start();
			/*
			 * Estabelecer conexao antes de retornar; Quando connectado, setar o id
			 * e socket da conexao;
			 */
			
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}

	public LSPClient(String host, int port) {
		this(host, port, null);
	}

	/**
	 * Devolve o Id da conexão
	 */
	public short getConnId() {
		return this.connId;
	}
	
	/**
	 * Devolve um vetor de bytes de uma mensagem enviada pelo lado servidor .
	 * Devolve null se a conexão for perdida .
	 */
	public byte[] read() {
		try {
			byte[] receiveData = new byte[1024];// recebimento

			// instancia o objecto onde vai receber a msg
			DatagramPacket receivePacket = new DatagramPacket(receiveData,
					receiveData.length);
			// bloqueia ate receber a mensagem
			this.serverSocket.receive(receivePacket);
			// Verifica ter recebido o pacote do servidor correto
			assert this.address == receivePacket.getAddress();
			assert this.port == receivePacket.getPort();

			return receiveData;

		} catch (SocketException e) {
			System.out.println("Socket: " + e.getMessage());
			return null;
		} catch (IOException e) {
			return null;
		}

	}

	/**
	 * Envia uma mensagen para o lado servidor como um vetor de bytes . Devolve
	 * exceção se a conexão for perdida .
	 */
	public void write(byte[] payload) {
		Pack packet = new Pack(this.connId, this.sequenceNumber, payload, this.address.getHostName(), this.port);
		this.sequenceNumber = (short) ((this.sequenceNumber + 1 ) % 65536);
		this.packetsToSend.add(packet);
	}

	/**
	 * Encerra a conexão .
	 */
	public void close() {
		// Fechar a conexao....
		this.active = false;
	}

	@Override
	public boolean isActive() {
		return this.active;
	}

	@Override
	public LinkedBlockingQueue<Pack> getPacketsToSend() {
		return this.packetsToSend;
	}

	@Override
	public short getPacketWaitingForAck() {
		return this.packetWaitingForACK;
	}

	@Override
	public void setPacketWaitingForAck(short sequenceNumber) {
		this.packetWaitingForACK = sequenceNumber;
	}
}
